Buffer 类的重要方法

Allocate()创建缓冲区

在使用 Buffer(缓冲区)之前,我们首先需要获取 Buffer 子类的实例对象,并且分配内存空间。

获取一个 Buffer 实例对象不是使用 new,而是调用 Buffer 子类的 allocate 方法:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
static IntBuffer intBuffer=null;

public void allocateTest(){  
    intBuffer = IntBuffer.allocate(20);  
    logger.info("---------intBuffer已创建---------");  
    logger.info("capacity="+intBuffer.capacity());  
    logger.info("position="+intBuffer.position());  
    logger.info("limit="+intBuffer.limit());  
}

在这个例子中调用了 InterBuffer.allocate(20) 创建了一个 IntBuffer 的实例对象,并且分配了 20 个 int 对象的空间也就是 20*4 个字节的空间。

例子的运行结果:

三月 25, 2023 2:01:03 下午 com.hingyun.bufferdemo.UseBuffer allocateTest
信息: ---------intBuffer已创建---------
三月 25, 2023 2:01:03 下午 com.hingyun.bufferdemo.UseBuffer allocateTest
信息: capacity=20
三月 25, 2023 2:01:03 下午 com.hingyun.bufferdemo.UseBuffer allocateTest
信息: position=0
三月 25, 2023 2:01:03 下午 com.hingyun.bufferdemo.UseBuffer allocateTest
信息: limit=20

从例子的运行结果中不难看出一个缓冲区新建后处于写入模式,其中 position 的写入位置为 0,最大的 limit 上线为容量 capacity 的初始化值。

Put 写入缓冲区

在调用 allocate 方法分配内存并返回实例对象后,缓冲区默认处于写模式,可以写入对象。写入缓冲区用到的是 put 方法,put 方法只需要一个参数就是需要写入的对象,但是这个参数的类型必须和缓冲区的类型保持一致。

现在我们调用之前的例子创建的 Buffer 对象,向里面写入 5 个 Int 类型的对象,也就是五个整数:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
public void putTest() {  
    allocateTest();  
    for (int i = 0; i < 5; i++) {  
        //写入一个整数到Buffer中  
        intBuffer.put(i);  
    }  
    logger.info("-------after put-------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
}

在例子中我们向 Buffer 对象中写入了五个整数元素,下面是日志输出结果:

三月 25, 2023 2:40:50 下午 com.hingyun.bufferdemo.UseBuffer putTest
信息: -------after put-------
三月 25, 2023 2:40:50 下午 com.hingyun.bufferdemo.UseBuffer putTest
信息: capacity=20
三月 25, 2023 2:40:50 下午 com.hingyun.bufferdemo.UseBuffer putTest
信息: position=5
三月 25, 2023 2:40:50 下午 com.hingyun.bufferdemo.UseBuffer putTest
信息: limit=20

其中 capacity 和 limit 的值都和初始化的值一样没有发生变化,而 position 则变成了 5 指向了第 6 个位置,也就是代表我们刚刚写入了五个元素到缓冲区中接下来一个元素的写入会在第六个位置。

Flip () 翻转

在往缓冲区写入数据后是不能直接从缓冲区中读取数据的,此时缓冲区还处于写模式下,如果需要读取数据则还需要将缓冲区转换为度模式,这个时候就需要用到我们的 flip()翻转方法了。

紧接着前面的例子这是个 flip () 的方法的演示:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
public void flipTest(){  
    putTest();  
    intBuffer.flip();  
    logger.info("-------after flip-------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
}

在调用 flip () 方法后缓冲区的属性就发生了变化:

信息: -------after flip-------
三月 25, 2023 2:52:23 下午 com.hingyun.bufferdemo.UseBuffer flipTest
信息: capacity=20
三月 25, 2023 2:52:23 下午 com.hingyun.bufferdemo.UseBuffer flipTest
信息: position=0
三月 25, 2023 2:52:23 下午 com.hingyun.bufferdemo.UseBuffer flipTest
信息: limit=5

从输出日志可以看出,在缓冲区翻转后缓冲区域的 capacity(容量)并没有发生变化,但是 position 的指向却到了 0 表示从头开始读取,而且 limit 的值也变成了之前 position 的值表示缓冲区中的最大可读数据量为 5。

由此我们可以得出缓冲区在写模式翻转成读模式时会先把 position 的值作为最大可读上限 limit 的值,然后设置 position 的值为 0 表示从头开始读。最后因为在例子中没有展示出来我提一嘴,在写模式翻转为读模式时会清除之前的 mark 标记,因为 mark 是写模式下的临时位置,如果在读模式下使用旧的 mark 标记会造成位置的混乱。

Flip () 方法的源码:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
public final Buffer flip() {  
	//把 position 的值作为最大可读上限 limit 的值
    limit = position;  
    //设置 position 的值为 0 表示从头开始读
    position = 0;  
    //清除mark标记
    mark = -1;  
    return this;  
}

Get () 从缓冲区读取

在调用 flip 方法将缓冲区翻转后,紧接着我们来看一下如何从缓冲区读取数据,读取数据非常的简单,只需要调用 get()方法每次从 position 的位置读取一个数据,同时缓冲区的属性也会自动进行相应的调整。

演示:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
public void getTest() {  
    flipTest();  
    //读两个  
    for (int i = 0; i < 2; i++) {  
        logger.info("i=" + intBuffer.get());  
    }  
    logger.info("-------after get 1-------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
    //再读三个  
    for (int i = 0; i < 3; i++) {  
        logger.info("i=" + intBuffer.get());  
    }  
    logger.info("-------after get 2-------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
  
}

下面是输出日志:

三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: -------从缓冲区读两个元素-------
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=0
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=1
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: -------after get 1-------
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: capacity=20
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: position=2
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: limit=5
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: -------从缓冲区读三个元素-------
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=2
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=3
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=4
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: -------after get 2-------
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: capacity=20
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: position=5
三月 25, 2023 3:20:35 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: limit=5

从输出日志我们可以看到读取操作会改变 position 的指向位置,而 limit 的值不会变,如果 position 和 limit 的值相等,则表示所有数据读取完毕,position 已经指向了一个没有数据的位置,已经无法读出任何数据了,如果此时再读就会抛出 BufferUnderflowException 异常。

那在我们在读完之后是否可以立刻进入写入模式对缓冲区进行写入呢?
这是不可以的,现在还是处于读模式,我们需要调用 Buffer.clearBuffer.compact 对缓冲区进行清空或者压缩,才能变为写入模式,这两个方法我们放在后面说。

既然读完后不能立刻进入写模式,那缓冲区可不可以重复读呢?

可以

Rewind()倒带

对于已经读完的数据,如果我们需要再读一遍的话可以使用 rewind()方法
演示:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
public void rewindTest() {  
    getTest();  
    intBuffer.rewind();  
    logger.info("-------after rewind-------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
}

输出日志:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
信息: -------after rewind-------
三月 25, 2023 3:43:08 下午 com.hingyun.bufferdemo.UseBuffer rewindTest
信息: capacity=20
三月 25, 2023 3:43:08 下午 com.hingyun.bufferdemo.UseBuffer rewindTest
信息: position=0
三月 25, 2023 3:43:08 下午 com.hingyun.bufferdemo.UseBuffer rewindTest
信息: limit=5

从日志中可以看出 rewind 主要是调整了 position 属性,让 position 指向第一个元素并清空 mark,其他的值不会发生改变
Rewin 源码:

public final Buffer rewind() {  
    position = 0;  
    mark = -1;  
    return this;  
}

Mark 和 Reset

Buffer.mark() 方法的作用就是将 position 的值保存起来放在 mark 属性中,让 mark 属性记住这个临时的位置,而 reset 方法则是把 mark 属性的值恢复到 position 中。

演示:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
public void markTest() {  
    flipTest();  
    for (int i = 0; i < 5; i++) {  
        int j = intBuffer.get();  
        //当到第三个元素时mark这个位置  
        //注意这里是在get操作后进行的mark操作,
        //所以获取的是get操作后的position值,也就是2+1=3
        if (i == 2) {  
            intBuffer.mark();  
            logger.info("---mark---");  
            logger.info("position=" + intBuffer.position());  
        }  
    }  
    logger.info("-----before reset------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
  
  
    intBuffer.reset();  
    logger.info("-----after reset------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
}

日志输出:

信息: ---mark---
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: position=3
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: -----before reset------
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: capacity=20
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: position=5
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: limit=5
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: -----after reset------
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: capacity=20
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: position=3
三月 25, 2023 4:33:25 下午 com.hingyun.bufferdemo.UseBuffer markTest
信息: limit=5

从日志中可以看到我们 mark 的位置是 3,在循环结束时 position 的指向为 5,而在我们调用 reset 方法后 position 又指向了 3,表示可以再次开始从第四个元素读取数据。

Clear 清空缓存区

在读取模式下,我们可以调用 clear() 方法将缓存区清空并切换为写入模式。这个方法会将 position 清零,limit 设置为 capacity 的最大容量值,可以一直写入,直到缓冲区写满。
演示:

/**  
 * @author: Ten  
 * @date: 2023/3/25 13:44  
 */
public void clearTest(){  
    //调用之前的读取方法  
    getTest();  
    //将缓存区清空  
    intBuffer.clear();  
    logger.info("-------after clear-------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
  
    //写入buffer  
    for (int i = 0; i < 5; i++) {  
        intBuffer.put(i);  
    }  
    logger.info("-------after put-------");  
    logger.info("capacity=" + intBuffer.capacity());  
    logger.info("position=" + intBuffer.position());  
    logger.info("limit=" + intBuffer.limit());  
  
}

日志输出:

三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=0
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=1
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: -------after get 1-------
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: capacity=20
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: position=2
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: limit=5
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: -------从缓冲区读三个元素-------
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=2
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=3
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: i=4
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: -------after get 2-------
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: capacity=20
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: position=5
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer getTest
信息: limit=5
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: -------after clear-------
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: capacity=20
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: position=0
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: limit=20
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: -------after put-------
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: capacity=20
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: position=5
三月 25, 2023 4:53:28 下午 com.hingyun.bufferdemo.UseBuffer clearTest
信息: limit=20

日志有一点长,不太适合阅读。总的来说 clear 方法的调用会让 position 值归零,limit = capacity,mark 值清空。
Clear 源码:

public final Buffer clear() {  
    position = 0;  
    limit = capacity;  
    mark = -1;  
    return this;  
}

小结

使用 Java NIO buffer 的基本步骤:

  1. 使用 allocate 创建子类实例
  2. 调用 put 向缓冲区写入数据
  3. 写入完成后,在读取之前调用 flip 方法将缓冲区转换为读取模式
  4. 调用 get 方法从缓冲区读取数据
  5. 读取完成后使用 clear 或者 compact 方法将缓冲区转换成写入模式


重要属性

Java NIO 的 Buffer 类是一个抽象类,它的内部是一个内存块(数组),与普通的 Java 数组不同的是:NIO Buffer 对象提供了一组更加有效的方法进行写入和读取的交替访问。为了记录读写的状态和位置 Buffer 类提供了一些重要的属性,其中有三个重要的成员属性 :capacity(容量)、position(读写位置)、limit(读写的限制)。另外还有一个 Mark 属性介意将当前的 position 记录下来,当需要时可以从 mark 标记中将 position 恢复到记录的位置。

注意:Buffer 类是一个非线程安全类

Buffer 类

Buffer 类是一个抽象类,对应于 Java 的主要数据类型,在 NIO 中有 8 种缓冲区类,分别如下:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。

前七种类型包含了 about_Java 所有能在 IO 中传输的基本数据类型,Boolean 类型无法传输。第八种 MappedByteBuffer 是专门用于内存映射的 ByteBuffer 类型。

Buffer 类的重要属性

Capacity

Capacity(容量)限制着当前的 Buffer 缓冲区能存入的数据量。比如在 Buffer 类初始化对象时设置 capacity=20,则这个 Buffer 对象只能存入最多 20 个数据并且 capacity 属性一但初始化就不能更改。因为 Buffer 类在初始化时会按照 capacity 分配内部的内存,在内存分配好之后自然是不能改变了,而且 capacity 容量不是指内存 byte 的数量,而是指的写入的对象的数量(如一个 IntBuffer 初始化时 capacity 是 20 则这个 Buffer 最多只能存入 20 个 int 对象)

Position

Buffer 类中的 position 属性表示当前读或者写的位置。在读写模式下,position 属性是不同的,当缓冲区的读写模式转变后,position 会进行调整。Buffer 使用 flip 方法进行读写模式的切换,当 Buffer 进行 flip 翻转后,position 会由原来的写入位置变成新的可读位置,也就是 0,表示从头开始读。

在写入模式下 Position 的变化规则:
  1. 在刚进入写模式下 position 值为 0,表示当前写入位置从头开始。
  2. 每有一个数据写入 Buffer 中 position 就会向后移动一个可写位置,也就是 +1
  3. 当 position=limit 时,缓冲区就已经无空间可写。
在读模式下 Position 的变化规则:
  1. 当缓冲区进入读模式时,position 会重置为 0,意味着从头开始读取
  2. 当从缓冲区读取时,也是从 position 的位置开始读,在读取数据后 position 会移动到下一个可读的位置。
  3. Position 最大的值为最大可读上限 limit,当 position=limit 时代表缓冲区已经没有数据可读了。

Limiti

Buffer 类中的 limit 属性表示读写的最大上线,在读、写模式下 limit 表示的含义是不同的。在写模式下 limit 表示这个 Buffer 可以写入数据的最大上限,在 Buffer 初始化时 limit 默认等于 capacity;在读模式下 limit 的含义为能从 Buffer 中读到多少数据量。

一般来说 Buffer 的使用都是先写入再读取,毕竟去读一个空的 Buffer 是没有意义的。当缓冲区写入完成后可以使用 flip 翻转方法将模式改为写模式,这时 limit 的值会设置成写模式下的 position 值作为可读取得最大上限。

Mark

这时一个比较简单的属性,它的作用主要是记录当前 position 的值,以备需要时调用 reset 方法将 mark 值恢复到 position 中。